package br.gov.dataprev.treinamento.ejb.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Schedule;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.validation.constraints.NotNull;

import br.gov.dataprev.treinamento.ejb.dao.ClienteDaoLocal;
import br.gov.dataprev.treinamento.ejb.dao.PedidoDaoLocal;
import br.gov.dataprev.treinamento.ejb.dao.ProdutoDaoLocal;
import br.gov.dataprev.treinamento.ejb.excecao.ExcecaoNegocio;
import br.gov.dataprev.treinamento.ejb.modelo.Cliente;
import br.gov.dataprev.treinamento.ejb.modelo.ItemPedido;
import br.gov.dataprev.treinamento.ejb.modelo.Pedido;
import br.gov.dataprev.treinamento.ejb.modelo.Produto;
import br.gov.dataprev.treinamento.ejb.modelo.SituacaoPedido;
import br.gov.dataprev.treinamento.ejb.service.local.FreteService;
import br.gov.dataprev.treinamento.ejb.service.local.PedidoServiceLocal;
import br.gov.dataprev.treinamento.ejb.service.remote.PedidoServiceRemote;
import br.gov.dataprev.treinamento.ejb.util.ValidacaoUtil;
import br.gov.dataprev.treinamento.ejb.validacao.grupos.Incluir;

@Stateless(mappedName = PedidoServiceRemote.EJB_NAME)
@Remote(PedidoServiceRemote.class)
@Local(PedidoServiceLocal.class)
@TransactionManagement(TransactionManagementType.CONTAINER)
public class PedidoServiceImpl implements PedidoServiceRemote,
		PedidoServiceLocal {

	@EJB
	private FreteService freteService;

	@EJB
	private PedidoDaoLocal pedidoDao;

	@EJB
	private ProdutoDaoLocal produtoDao;

	@EJB
	private ClienteDaoLocal clienteDao;

	@Resource(mappedName = "java:app/jms/PagamentoCartaoFactory")
	private QueueConnectionFactory filaCartaoFactory;

	@Resource(mappedName = "java:app/jms/PagamentoCartaoQueue")
	private Queue filaCartao;

	@Resource(mappedName = "mail/LojaMail")
	private javax.mail.Session mySession;

	@TransactionAttribute(TransactionAttributeType.SUPPORTS)
	public void validarNovoPedido(Pedido pedido) throws Exception {
		if (pedido.getFormaPagamento() == null)
			throw new Exception("Informe a forma de pagamento.");

		if (pedido.getItens() == null || pedido.getItens().size() == 0)
			throw new Exception("O Carrinho está vazio");

		if (pedido.getCliente() == null)
			throw new Exception("Cliente não informado");
	}

	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public Pedido finalizarPedido(Pedido pedido) throws ExcecaoNegocio {
		
		ValidacaoUtil.getInstance().validar(pedido, Incluir.class);

		// validar cliente
		Cliente cliente = clienteDao.obterCliente(pedido.getCliente().getId());
		if (cliente == null) {
			throw new ExcecaoNegocio("Cliente não cadastrado");
		}
		pedido.setCliente(cliente);

		// consolidar itens
		Map<Produto, ItemPedido> itens = new HashMap<Produto, ItemPedido>();
		for (ItemPedido ip : pedido.getItens()) {
			if (itens.containsKey(ip.getProduto())) {
				ItemPedido aux = itens.get(ip.getProduto());
				aux.setQuantidade(aux.getQuantidade() + ip.getQuantidade());
			} else {
				itens.put(ip.getProduto(), ip);
			}
		}
		pedido.setItens(new ArrayList<ItemPedido>(itens.values()));

		// baixar produtos do estoque
		try {
			baixarEstoque(pedido);
		} catch (Exception e) {
			e.printStackTrace();
		}

		pedido.setSituacao(SituacaoPedido.AGUARDANDO_CONFIRMACAO_PGTO);
		pedido.setDataCadastro(new Date());
		pedido.setValorFrete(freteService.calcularFrete(pedido));

		// persistir pedido
		pedidoDao.incluir(pedido);

		// TODO processar pagamento
		confirmarPagamentoCartao(pedido);

		// enviar email de confirmação
		// enviarEmailPedidoRecebido(pedido);

		return pedido;

	}

	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	private void baixarEstoque(@NotNull Pedido pedido) throws Exception {
		// Verificando estoque
		for (ItemPedido ip : pedido.getItens()) {
			Produto prod = produtoDao.obterProduto(ip.getProduto().getId());
			if (prod != null) {
				if (prod.getEstoque() < ip.getQuantidade()) {
					throw new Exception("Estoque insuficiente para o produto "
							+ prod.getNome());
				} else {
					prod.setEstoque(prod.getEstoque() - ip.getQuantidade());
					produtoDao.alterar(prod);
				}
			} else {
				throw new Exception("Produto " + ip.getProduto().getNome()
						+ "inválido");
			}
		}
	}

	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public Pedido cancelarPedido(Pedido pedido) {

		// TODO obter do banco

		switch (pedido.getSituacao()) {

		case AGUARDANDO_CONFIRMACAO_PGTO:
			pedido.setSituacao(SituacaoPedido.CANCELADO);
			break;

		case CONFIRMADO:
		case PRONTO_ENVIO:
			pedido.setSituacao(SituacaoPedido.CANCELADO);
			// TODO providenciar extorno do pagamento
			break;

		case ENVIADO_TRANSPORTADORA:
		case ENTREGUE:
			pedido.setSituacao(SituacaoPedido.CANCELADO);
			// TODO solicitar devoluçao
			break;

		case CANCELADO:
			// TODO nao faz nada
		}

		// TODO persiste o pedido;
		pedidoDao.incluir(pedido);

		return pedido;
	}

	@TransactionAttribute(TransactionAttributeType.SUPPORTS)
	public Pedido obterPedido(Long id) {
		return null;
	}

	@TransactionAttribute(TransactionAttributeType.SUPPORTS)
	public List<Pedido> obterPedidos(Cliente c) {
		return null;
	}

	private void enviarEmail(String to, String subject, String body)
			throws AddressException, MessagingException {
		Message message = new MimeMessage(mySession);
		message.setFrom(new InternetAddress("leoeri@gmail.com"));
		Address toAddress = new InternetAddress(to);
		message.addRecipient(Message.RecipientType.TO, toAddress);
		message.setSubject(subject);
		message.setContent(body, "text/plain");
		Transport.send(message);
	}

	private void enviarEmailPedidoRecebido(Pedido p) throws AddressException,
			MessagingException {
		String msg = "Prezado cliente,\n"
				+ "Seu pedido n. "
				+ p.getId()
				+ " foi recebido e será efetivado após a confirmação do pagamento"
				+ " e conforme disponibilidade dos produtos em estoque.\n";

		enviarEmail(p.getCliente().getEmail(), "Pedido recebido", msg);

	}

	private void enviarEmailCancelado(Pedido p, String motivoCancelamento)
			throws AddressException, MessagingException {

		String msg = "Prezado cliente,\n" + "Informamos que seu pedido n. "
				+ p.getId() + " foi cancelado.\nMotivo:  " + motivoCancelamento;

		enviarEmail(p.getCliente().getEmail(), "Pedido cancelado", msg);

	}

	private void confirmarPagamentoCartao(Pedido pedidoSerializable) {
		try {
			QueueConnection queueCon = filaCartaoFactory
					.createQueueConnection();

			QueueSession queueSession = queueCon.createQueueSession(false,
					javax.jms.Session.AUTO_ACKNOWLEDGE);

			QueueSender queueSender = queueSession.createSender(null);

			ObjectMessage message = queueSession
					.createObjectMessage(pedidoSerializable);

			queueSender.send(filaCartao, message);

			System.out.println("Enviando pedido para pagamento com cartao");

			queueCon.close();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Schedule(dayOfMonth="*", month="*", year="*", hour="1", minute="22")
	private void confirmarPagamentosBoleto() {
		
		System.out.println("Executando método agendado confirmarPagamentosBoleto");
		//TODO obter retorno do banco
		//TODO obter pedidos
		//TODO setar situacao pedido para CONFIRMADO
		//TODO salvar pedido
		
	}
	

}
